#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <pthread.h>
#include <semaphore.h>
#include <math.h>
#include <time.h>
#include <sys/syscall.h>

#define N 2 //constant representing # of students
#define M 3 //constant representing # of books
#define T 300  //constant for the amount of time
#define TRUE 1
#define FALSE 0

#define IA 16807
#define IM 2147483647
#define AM (1.0/IM)
#define IQ 127773
#define IR 2836
#define NTAB 32
#define NDIV (1+(IM-1)/NTAB)
#define EPS 1.2e-7
#define RNMX (1.0 - EPS)

/* GLOBAL VARIABLES */
typedef int boolean;

// Define book structure
typedef struct {
        int num_pages;
	boolean selected;
	boolean returned;
} booktype;

// Declare semaphores and total_pages global variable
sem_t sem_people, sem_books, sem_check;
int total_pages;
booktype books[M];
int numPeople;

// Functions used 
void *student_thread(void *param);
void meet_at_the_library();
booktype* pick_a_book();
void read_book(booktype* book);
boolean share_impressions(booktype* book);
void return_book(booktype* book);
void initializeBooks(booktype* book);
float normalRandom(long *idum);
float exponentialRandom(long *idum);



int main()
{
        int i; //used for counter when creating threads
        int j; //used for counter when joining threads
	int k; //used to instantiate books linked list
	float normRandom;
	int rounded;
	long someNum = 500;
	int meanPages = 400;

        //initializing total pages read and semaphores
        total_pages = 0;
	numPeople = 0;
        sem_init(&sem_people,0,N);
        sem_init(&sem_books,0,M);
	sem_init(&sem_check,0,1);
		
	// This makes M number of books, stores them into an array, and intializes all values
	for (k =0; k < M; k++)
	{
		
		normRandom = 400 + normalRandom(&someNum)*200;
		rounded = round(normRandom);
		if (normRandom < 10)
		{
			books[k].num_pages = meanPages;
		}
		else
		{
			books[k].num_pages = rounded;
		}
		books[k].selected=FALSE;
		books[k].returned=FALSE;
	}
		
        //creating N student threads
        pthread_t tid[N];
        pthread_attr_t pt_attribs;
		
        for (i=0; i<N; i++)
        {
                pthread_attr_init(&pt_attribs);
                pthread_create(&(tid[i]), &pt_attribs, student_thread, NULL);
        }

        for (j=0; j<N; j++)
                pthread_join(tid[j], NULL);

        return 0;
}

//function for threads provided by Dr. Varsamopoulos
void *student_thread(void *param)
{
        booktype* book = (booktype*) malloc(sizeof(booktype));
        boolean done = FALSE;
        meet_at_the_library();
        do {
                book = pick_a_book();
                read_book(book);
                done = share_impressions(book);
                return_book(book);
        }while(!done);
        pthread_exit(0);
}

//This function returns after all student threads enter it
void meet_at_the_library()
{

        // Time information
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;
	boolean everyone_here = FALSE;
	int value;		

        // Prints out current time each thread enters
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering meet_at_the_library()\n",buffer,(long int) syscall(SYS_gettid));
       
	//waits for all of the student threads to arrive before moving on
	sem_wait(&sem_check);
	numPeople++;
	sem_post(&sem_check);

	while(numPeople < N)
	{
		usleep(1);
	}
		
	usleep(2);

        // Prints out time each thread exits
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
	printf("%s Thread %ld : Exiting meet_at_the_library() ",buffer,(long int) syscall(SYS_gettid));

        // Prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);
}

booktype* pick_a_book()
{
        // Time infomation
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;
		
	// Variables associated with selecting a book
	int i,j;
	int numOfAvailableBooks;
	booktype *book = (booktype *) malloc(sizeof(booktype));
	numPeople = 0;
		
	//prints out current time & function entered
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering pick_a_book()\n",buffer,(long int) syscall(SYS_gettid));	
	
	//checks how many books are available and assigns one
	//randomly to the student thread
	sem_wait(&sem_check);
		
	numOfAvailableBooks = 0;
	for(i=0; i<M; i++)
	{
		if(books[i].selected==FALSE && books[i].returned==FALSE)
			numOfAvailableBooks++;
	}

	srand(time(NULL));

	if (numOfAvailableBooks>0)
	{
		do {
			j = rand()%M;
		}while(books[j].selected==TRUE || books[j].returned==TRUE);

		books[j].selected = TRUE;
		book = &books[j];
	}
	else
	{
		book = NULL;
	}
		
	sem_post(&sem_check);

        //prints out time & function exiting
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting pick_a_book() ",buffer,(long int) syscall(SYS_gettid));

        //prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);

	//Return book selected by this specific student thread
	return book;
}

void read_book(booktype* book)
{
        // Time information
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;

        // Prints out current time & function entered
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering read_book()\n",buffer,(long int) syscall(SYS_gettid));
		
	/* Each student thread can be in read_book at the same time, 
	and when each are done reading their book they will leave and 
	go to the share_impressions function */
	if(book != NULL)
	{
	long daPages = (long)books->num_pages;
	float sleepTime = T * (books->num_pages) * exponentialRandom(&daPages);
	int newSleepTime = (int)sleepTime;
	printf ("reading for %d us\n", newSleepTime);

	usleep(newSleepTime);
	}	
        // Prints out time & function exiting
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting read_book() ",buffer,(long int) syscall(SYS_gettid));

        // Prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);

}

boolean share_impressions(booktype* book)
{
        // Time information
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;
	boolean everyone_here = FALSE;
		
	// Variable used with values of sem_people
        int value;
	int availableBooks;
	int i;

        // Prints out current time & function entered
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering share_impressions()\n",buffer,(long int) syscall(SYS_gettid));

        // Waits for all of the people to arrive and totals the amount of pages read
	sem_wait(&sem_people);
        sem_wait(&sem_check);
	if(book != NULL)
	{
        	total_pages += book->num_pages;
	}
	sem_getvalue(&sem_check,&value);
	sem_post(&sem_check);
        while (value > 0)
        {
		usleep(1);
        }
	
	//sleeps for a specific amount of time to share
	long daPages = (long)total_pages;
	float timeToSleep = T * total_pages * exponentialRandom(&daPages);
	int newTimeToSleep = (int)timeToSleep;
	
	usleep (newTimeToSleep);

        sem_post(&sem_people);
	total_pages=0;
		
        // Prints out time & function exiting
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting share_impressions() ",buffer,(long int) syscall(SYS_gettid));

        // Prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);

	//checks whether all books have been returned
	sem_wait(&sem_check);
	availableBooks = 0;
	for(i=0;i<M;i++)
	{
		if(books[i].selected == TRUE)
			availableBooks++;
	}
	if(availableBooks >= M)
	{
		sem_post(&sem_check);
		return TRUE;
	}
	else
	{
		sem_post(&sem_check);
		return FALSE;
	}
	return FALSE;
}

void return_book(booktype* book)
{
        // Time information
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;
	int i;
		
        // Prints out current time & function entered
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering return_book()\n",buffer,(long int) syscall(SYS_gettid));

	//sets flag to indicate the book has been returned		
	book->returned = TRUE;      
 
        // Prints out time & function exiting
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting return_book() ",buffer,(long int) syscall(SYS_gettid));

        // Prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);

}

//Needed to be implemented in the exponentialRandom function and
//the normalRandom function
float ran1(long *idum)
{
	int j;
	long k;
	static long iy = 0;
	static long iv[NTAB];
	float temp;
	
	if (*idum <= 0 || *idum <= !iy) 
	{
		if (-(*idum) < 1)
		{
			*idum = 1;
		}
		else 
		{ 
			*idum = -(*idum);
		}
		for (j = NTAB +7; j>=0; j--)
		{
			k = (*idum)/IQ;
			*idum = IA*(*idum-k*IQ)-IR*k;
			if (*idum < 0) *idum += IM;
			if (j < NTAB) iv[j] = *idum;
		}
		iy = iv[0];
	}
	
	k=(*idum)/IQ;
	*idum=IA*(*idum-k*IQ)-IR*k;
	
	if (*idum < 0){
		*idum += IM;
	}
	
	j=iy/NDIV;
	iy=iv[j];
	iv[j] = *idum;
	
	if ((temp=AM*iy) > RNMX)
	{
		return RNMX;
	}
	else {
	return temp;
	}
}

float normalRandom(long *idum)
{
	float ran1(long *idum);
	static int iset = 0;
	static float gset;
	float fac, rsq, v1, v2;
	
	if (*idum < 0) iset = 0;
	if (iset == 0) {
		do {
			v1 = 2.0*ran1(idum) - 1.0;
			v2 = 2.0*ran1(idum) - 1.0;
			rsq = v1*v1 + v2*v2;
		}
		while (rsq >= 1.0 || rsq == 0.0);
		fac = sqrt(-2.0 * log(rsq)/rsq);
		gset = v1*fac;
		iset = 1;
		return v2*fac;
	}
	else {
		iset = 0;
		return gset;
	}
}

float exponentialRandom(long *idum)
{
	float ran1(long *idum);
	float dum;
	
	do
		dum = ran1(idum);
	while (dum == 0.0);
	return -log(dum);
}

